package com.neusoft.databus.core.service.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QPageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import com.excel.poi.ExcelBoot;
import com.excel.poi.entity.ErrorEntity;
import com.excel.poi.function.ExportFunction;
import com.excel.poi.function.ImportFunction;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.neusoft.bizcore.common.utils.DateUtil;
import com.neusoft.bizcore.web.exception.CustomRuntimeException;
import com.neusoft.bizcore.web.support.Searchable;
import com.neusoft.bizcore.web.utils.ExcelPoiBoot;
import com.neusoft.bizcore.web.utils.ExcelUtil;
import com.neusoft.bizcore.web.utils.JsonUtils;
import com.neusoft.databus.common.constant.DatabusErrorCode;
import com.neusoft.databus.common.dto.DictionaryDTO;
import com.neusoft.databus.common.dto.MetricDTO;
import com.neusoft.databus.common.dto.MetricGroupDTO;
import com.neusoft.databus.common.enums.DictionaryType;
import com.neusoft.databus.common.enums.MetricValueType;
import com.neusoft.databus.common.enums.Protocol;
import com.neusoft.databus.common.model.Dictionary;
import com.neusoft.databus.common.model.Metric;
import com.neusoft.databus.common.model.MetricGroup;
import com.neusoft.databus.core.constant.DatabusCoreConstant;
import com.neusoft.databus.core.converter.MetricConverter;
import com.neusoft.databus.core.converter.MetricGroupConverter;
import com.neusoft.databus.core.repository.DictionaryRepository;
import com.neusoft.databus.core.repository.MetricGroupRepository;
import com.neusoft.databus.core.repository.MetricRepository;
import com.neusoft.databus.core.service.DictionaryService;
import com.neusoft.databus.core.service.MetricGroupService;
import com.neusoft.databus.core.service.MetricService;
import com.neusoft.databus.core.vo.MetricPortVO;

import lombok.extern.slf4j.Slf4j;

/**
 * @author naxiang
 */
@Slf4j
@Service
@Transactional
public class MetricServiceImpl implements MetricService {

    @Autowired
    private MetricGroupService metricGroupService;
    @Autowired
    private MetricConverter metricConverter;
    @Autowired
    private MetricRepository metricRepository;
    @Autowired
    private MetricGroupRepository metricGroupRepository;
    @Autowired
    private MetricGroupConverter metricGroupConverter;
    @Autowired
    private DictionaryService dictionaryService;
    @Autowired
    private DictionaryRepository dictionaryRepository;
    @Value("${databus.core.execution_cycle: 120}")
    private long execution_cycle;
    private static int bacheSize = 300;

    private List<DictionaryDTO> dictionaries = Lists.newArrayList();

    @Override
    public MetricDTO findById(final Long id) {
        try {
            final Metric model = this.metricRepository.getOne(id);
            return this.metricConverter.toDTO(model);
        } catch (final EntityNotFoundException e) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_404001, new Object[] {id });
        }
    }

    @Override
    public MetricDTO findByCode(final String code) {
        final Metric model = this.metricRepository.findByCode(code);
        if (null == model) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_404001, new Object[] {code });
        }
        return this.metricConverter.toDTO(model);
    }

    @Override
    public MetricDTO findByResourceAndCode(String resourceCode, String code) {
        final Searchable mgSearchable = new Searchable();
        mgSearchable.put("resource", resourceCode);
        final List<MetricGroupDTO> mgDTOs = this.metricGroupService.search(mgSearchable);
        Metric mMetric = null;
        for (final MetricGroupDTO mgt : mgDTOs) {
            final Searchable mSearchable = new Searchable();
            mSearchable.put("metricGroup", mgt.getCode());
            final List<Metric> metrics = this.metricRepository.search(mSearchable);
            for (final Metric metric : metrics) {
                // modified at 2020-01-14 哈尔滨项目使用variable作为key存储采集数据
                // if (code.equals(metric.getCode())) {
                if (code.equals(metric.getVariable())) {
                    mMetric = metric;
                    break;
                }
            }
            if (null != mMetric) {
                break;
            }
        }
        if (null != mMetric) {
            return this.metricConverter.toDTO(mMetric);
        }
        throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_404001, new Object[] {code });
    }

    @Override
    public List<MetricDTO> findAll() {
        final List<Metric> models = this.metricRepository.findAll();
        return this.metricConverter.toListDTO(models);
    }

    @Override
    public Page<MetricDTO> search(final Pageable pageable, final Searchable searchable) {
        final Page<Metric> models = this.metricRepository.search(pageable, searchable);
        return this.metricConverter.toPageDTO(models);
    }

    @Override
    public List<MetricDTO> search(final Searchable searchable) {
        final List<Metric> models = this.metricRepository.search(searchable);
        return this.metricConverter.toListDTO(models);
    }

    @Override
    public MetricDTO create(final MetricDTO dto) {
        final Metric model = this.metricConverter.toModel(dto);
        this.metricRepository.save(model);
        return this.metricConverter.toDTO(model);
    }

    @Override
    public MetricDTO update(final String code, final MetricDTO dto) {
        Metric model = this.metricRepository.findByCode(code);
        if (null == model) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_404001, new Object[] {code });
        }
        dto.setId(model.getId());
        model = this.metricConverter.toModel(dto);
        this.metricRepository.save(model);
        return this.metricConverter.toDTO(model);
    }

    @Override
    public void deleteById(final Long id) {
        this.metricRepository.deleteById(id);
    }

    @Override
    public void deleteByCode(final String code) {
        this.metricRepository.deleteByCode(code);
    }

    private void batchSave(final List<Metric> metrics) {
        final int listsize = metrics.size();
        if (listsize == 0) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_406113);
        } else {
            // 批量存储的集合
            final List<Metric> data = new ArrayList<>();
            // 批量存储
            metrics.forEach(metric -> {
                data.add(metric);
                if ((data.size() % MetricServiceImpl.bacheSize) == 0) {
                    this.metricRepository.saveAll(data);
                    data.clear();
                }
            });
            // 将剩下的数据也导入
            if (!data.isEmpty()) {
                this.metricRepository.saveAll(data);
            }
        }
    }

    @Override
    public void deleteByIdIn(final List<Long> ids) {
        this.metricRepository.deleteByIdIn(ids);
    }

    @Override
    public void importTpl(final MultipartFile tpl) {

        final Map<String, MetricGroup> metricGroupCache = Maps.newHashMap();
        final List<Metric> metricCache = Lists.newArrayList();
        final List<String> units = Lists.newArrayList();
        this.dictionaries =
                this.dictionaryService.findByDictionaryTypeAndParentIsNull(DictionaryType.unit);
        final Map<String, String> dictPairs = Maps.newHashMap();
        this.dictionaries.stream().forEach(d -> {
            dictPairs.put(d.getCode().toUpperCase(), d.getText());
        });
        final StringBuffer errmsg = new StringBuffer();
        try {
            ExcelPoiBoot.ImportBuilder(tpl.getInputStream(), MetricPortVO.class)
                    .importExcel(new ImportFunction<MetricPortVO>() {
                        @Override
                        public void onProcess(final int sheetIndex, final int rowIndex,
                                final MetricPortVO metricPortVO) {
                            //处理单位，获得表中未保存的单位信息
                            if (StringUtils.isNotEmpty(metricPortVO.getUnit())) {
                                String unit = metricPortVO.getUnit();
                                if (unit.contains(DatabusCoreConstant.DICTIONARY_SPLITOR)) {
                                    unit = unit.split(DatabusCoreConstant.DICTIONARY_SPLITOR)[0];
                                }
                                if (!dictPairs.containsKey(unit.toUpperCase())) {
                                    if (!units.contains(metricPortVO.getUnit())) {
                                        units.add(metricPortVO.getUnit());
                                    }
                                } else {
                                    unit = dictPairs.get(unit.toUpperCase());
                                }
                                metricPortVO.setUnit(unit);
                            }
                            //导入vo转实体
                            final Metric metric = MetricServiceImpl.this.metricConverter.toModel(metricPortVO);
                            if (StringUtils.isNoneBlank(metricPortVO.getMetricGroupName())
                                    && StringUtils.isNoneBlank(metricPortVO.getProtocol())) {//模板中指标组信息
                                //同一个指标组下的指标合并
                                final String metricGroupKey =
                                        metricPortVO.getMetricGroupName()
                                                + DatabusCoreConstant.DICTIONARY_SPLITOR
                                                + metricPortVO.getProtocol();
                                if (metricGroupCache.containsKey(metricGroupKey)) {
                                    metricGroupCache.get(metricGroupKey).getMetrics().add(metric);
                                } else {
                                    final MetricGroup metricGroup =
                                            MetricServiceImpl.this.metricGroupConverter.toModel(metricPortVO);
                                    //metricGroup.setExecutionCycle(MetricServiceImpl.this.execution_cycle);
                                    metricGroup.getMetrics().add(metric);
                                    metricGroupCache.put(metricGroupKey, metricGroup);
                                }
                            } else {//没有指标组信息，只导入指标
                                metricCache.add(metric);
                            }
                        }

                        @Override
                        public void onError(final ErrorEntity errorEntity) {
                            MetricServiceImpl.log.error("指标导入错误：{}", JsonUtils.pojoToJson(errorEntity));
                            errmsg.append(errorEntity.getErrorMessage()).append("\n");
                        }
                    });
        } catch (final IOException e) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_406112, e.getMessage());
        }
        if (StringUtils.isNotBlank(errmsg.toString())) {
            throw new CustomRuntimeException(DatabusErrorCode.ERR_CODE_406112,
                    errmsg.toString());
        } else {
            final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
            singleThreadExecutor.execute(new ImportRunnable(metricCache, metricGroupCache, units));
        }
    }

    /**
     * 新建thred入库
     *
     * @author naxiang
     */
    @Transactional
    private class ImportRunnable implements Runnable {
        private final List<Metric> metricCache;
        private final Map<String, MetricGroup> metricGroupCache;
        private final List<String> units;

        public ImportRunnable(List<Metric> metricCache, Map<String, MetricGroup> metricGroupCache, List<String> units) {
            super();
            this.metricCache = metricCache;
            this.metricGroupCache = metricGroupCache;
            this.units = units;
        }

        @Override
        public void run() {
            final long start = System.currentTimeMillis();
            try {
                if (this.metricCache.size() > 0) {
                    MetricServiceImpl.this.batchSave(this.metricCache);
                }
                if (!this.metricGroupCache.isEmpty() && (this.metricGroupCache.size() > 0)) {
                    MetricServiceImpl.this
                            .addMetricGroup(this.metricGroupCache.values().stream().collect(Collectors.toList()));
                }
                //保存单位
                MetricServiceImpl.this.saveDictionaries(this.units);
            } catch (final Exception e) {
                MetricServiceImpl.log.error(e.getMessage(), e);
            }
            MetricServiceImpl.log.debug("指标导入结束:{}", System.currentTimeMillis() - start);
        }
    }

    private void addMetricGroup(final List<MetricGroup> models) {
        models.forEach(model -> {
            final MetricGroup metricGroup =
                    this.metricGroupRepository.findByNameAndProtocol(model.getName(), model.getProtocol());
            if (null != metricGroup) {
                this.metricRepository.deleteInBatch(metricGroup.getMetrics());
                metricGroup.getMetrics().clear();
                metricGroup.getMetrics().addAll(model.getMetrics());
                metricGroup.setUpdateTime(new Date());
                model = metricGroup;
            }
            this.metricGroupRepository.save(model);
        });
    }

    private void saveDictionaries(final List<String> units) {
        if (units.size() > 0) {
            final int size = this.dictionaries.size();
            int sort = this.dictionaries.get(size - 1).getSort();
            for (final String unit : units) {
                sort++;
                final Dictionary dictionary = new Dictionary();
                dictionary.setDictionaryType(DictionaryType.unit);
                if (unit.contains(DatabusCoreConstant.DICTIONARY_SPLITOR)) {
                    dictionary.setCode(unit.split(DatabusCoreConstant.DICTIONARY_SPLITOR)[0]);
                    dictionary.setText(unit.split(DatabusCoreConstant.DICTIONARY_SPLITOR)[1]);
                } else {
                    dictionary.setCode(unit);
                    dictionary.setText(unit);
                }
                dictionary.setSort(sort);
                this.dictionaryRepository.save(dictionary);
            }
        }
    }

    @Override
    public SXSSFWorkbook getTpl() {
        final String sheetName = DatabusCoreConstant.TPL_NAME_METRIC;
        final Map<Integer, String[]> dropDowns = Maps.newHashMap();

        final String[] protocols = new String[Protocol.values().length];
        for (int i = 0; i < protocols.length; i++) {
            protocols[i] = Protocol.values()[i].toString().toLowerCase();
        }

        dropDowns.put(1, protocols);

        final String[] metricValueTypes = new String[MetricValueType.values().length];
        for (int i = 0; i < metricValueTypes.length; i++) {
            metricValueTypes[i] = MetricValueType.values()[i].toString().toLowerCase();
        }

        dropDowns.put(5, metricValueTypes);
        final List<DictionaryDTO> dictionaries =
                this.dictionaryService.findByDictionaryTypeAndParentIsNull(DictionaryType.unit);
        final String[] dictionaryDropDown = new String[dictionaries.size()];
        for (int i = 0; i < dictionaries.size(); i++) {
            final DictionaryDTO dictionary = dictionaries.get(i);
            dictionaryDropDown[i] =
                    dictionary.getCode() + DatabusCoreConstant.DICTIONARY_SPLITOR + dictionary.getText();
        }
        dropDowns.put(13, dictionaryDropDown);
        final SXSSFWorkbook workbook =
                ExcelUtil.createExcelTemplate(sheetName, DatabusCoreConstant.METRIC_TPL_COLUMN, dropDowns);
        return workbook;
    }

    @Override
    public void export(final Searchable searchable, final Pageable pageable, final HttpServletResponse response) {
        final StringBuffer fileName = new StringBuffer("指标");
        fileName.append(DatabusCoreConstant.EXPORT_SPLITOR)
                .append(DateUtil.currentTimestamp2String(DateUtil.PATTERN_NULL_CONN));
        ExcelBoot
                .ExportBuilder(response, fileName.toString(), MetricPortVO.class)
                .exportMultiSheetResponse(searchable, new ExportFunction<Searchable, Metric>() {
                    @Override
                    public List<Metric> pageQuery(final Searchable searchable, final int pageNum, final int pageSize) {
                        final QPageRequest qPageRequest = new QPageRequest(pageNum - 1, pageSize);
                        final Page<Metric> models =
                                MetricServiceImpl.this.metricRepository.search(qPageRequest, searchable);
                        return models.getContent();
                    }

                    @Override
                    public MetricPortVO convert(final Metric metric) {
                        return MetricServiceImpl.this.metricConverter.toVO(metric);
                    }
                });
    }

}
